//################################################################################
// MIT License
// Copyright (c) 2024 ZhangYihua
//
// Change Logs:
// Date           Author       Notes
// 2023-05-14     ZhangYihua   first version
//
// Description  : 
//################################################################################

module cos_sin_tab #(
parameter           WAVE_DW                 = 12,
parameter           WAVE_AM                 = 2047,     // WAVE_AM < 2^(WAVE_DW-1)
parameter           NEG_SIN                 = 1'b0,     // 1'b0:normal; 1'b1:invert sin;
parameter           PHS_DW                  = 12,
parameter           CH_NUM                  = 1
) ( 
input                                       rst_n,
input                                       clk,
input                                       cke,

input               [CH_NUM*PHS_DW-1:0]     phase,      // (0:2*PI)
output  reg         [CH_NUM*WAVE_DW-1:0]    cos_dat,    // [-WAVE_AM:WAVE_AM]
output  reg         [CH_NUM*WAVE_DW-1:0]    sin_dat     // [-WAVE_AM:WAVE_AM]
);

//################################################################################
// define local varialbe and localparam
//################################################################################
localparam          TAB_DW                  = $clog2(WAVE_AM+1);
localparam          TAB_AW                  = PHS_DW-3;     // range (0:pi/4)
localparam          TAB_NUM                 = 1<<TAB_AW;
localparam          PI                      = 3.1415926536;
localparam [TAB_DW-1:0]     TAB_MAX         = WAVE_AM;
localparam [WAVE_DW-1:0]    WAVE_MAX        = WAVE_AM;

wire                [TAB_AW-1:0]            rd_addr[CH_NUM-1:0];
wire                [CH_NUM-1:0]            rd_swap;
wire                [CH_NUM-1:0]            rd_cinv;
wire                [CH_NUM-1:0]            rd_sinv;
reg                 [TAB_DW-1:0]            rd_cos_1d[CH_NUM-1:0];
reg                 [TAB_DW-1:0]            rd_sin_1d[CH_NUM-1:0];
reg                 [CH_NUM-1:0]            rd_swap_1d;
reg                 [CH_NUM-1:0]            rd_cinv_1d;
reg                 [CH_NUM-1:0]            rd_sinv_1d;
wire        signed  [WAVE_DW-1:0]           rd_pcos_1d[CH_NUM-1:0];
wire        signed  [WAVE_DW-1:0]           rd_ncos_1d[CH_NUM-1:0];
wire        signed  [WAVE_DW-1:0]           rd_psin_1d[CH_NUM-1:0];
wire        signed  [WAVE_DW-1:0]           rd_nsin_1d[CH_NUM-1:0];

wire                [TAB_NUM*TAB_DW-1:0]    cos_ary;
wire                [TAB_NUM*TAB_DW-1:0]    sin_ary;
reg                 [TAB_DW-1:0]            cos_rom[TAB_NUM-1:0];
reg                 [TAB_DW-1:0]            sin_rom[TAB_NUM-1:0];

//################################################################################
// main
//################################################################################

genvar g0;
generate for (g0=0; g0<CH_NUM; g0=g0+1) begin:G_CH
        
    // phase[0:2*PI] map to rd_addr[0:PI/4]
    cos_sin_map #(
        .NEG_SIN                        (NEG_SIN                        ),	// 1'b0:normal; 1'b1:invert sin;
        .PHS_DW                         (PHS_DW                         )
    ) u_cos_sim_map ( 
        .rst_n                          (rst_n                          ),
        .clk                            (clk                            ),
        .cke                            (cke                            ),

        .phase                          (phase[g0*PHS_DW+:PHS_DW]       ),
        .rd_addr                        (rd_addr[g0]                    ),
        .rd_swap                        (rd_swap[g0]                    ),
        .rd_cinv                        (rd_cinv[g0]                    ),
        .rd_sinv                        (rd_sinv[g0]                    )
    );

    always@(posedge clk or negedge rst_n) begin
        if (rst_n==1'b0) begin
            rd_cos_1d[g0] <=`U_DLY TAB_MAX;
            rd_sin_1d[g0] <=`U_DLY {TAB_DW{1'b0}};
        end else if (cke==1'b1) begin
            rd_cos_1d[g0] <=`U_DLY cos_rom[rd_addr[g0]];
            rd_sin_1d[g0] <=`U_DLY sin_rom[rd_addr[g0]];
        end else
            ;
    end

    always@(posedge clk or negedge rst_n) begin
        if (rst_n==1'b0) begin
            rd_swap_1d[g0] <=`U_DLY 1'b0;
            rd_cinv_1d[g0] <=`U_DLY 1'b0;
            rd_sinv_1d[g0] <=`U_DLY 1'b0;
        end else if (cke==1'b1) begin
            rd_swap_1d[g0] <=`U_DLY rd_swap[g0];
            rd_cinv_1d[g0] <=`U_DLY rd_cinv[g0];
            rd_sinv_1d[g0] <=`U_DLY rd_sinv[g0];
        end else
            ;
    end

    assign rd_pcos_1d[g0] = {{WAVE_DW-TAB_DW{1'b0}}, rd_cos_1d[g0]};
    assign rd_ncos_1d[g0] = $signed({WAVE_DW{1'b0}}) - rd_pcos_1d[g0];

    assign rd_psin_1d[g0] = {{WAVE_DW-TAB_DW{1'b0}}, rd_sin_1d[g0]};
    assign rd_nsin_1d[g0] = $signed({WAVE_DW{1'b0}}) - rd_psin_1d[g0];

    always@(posedge clk or negedge rst_n) begin
        if (rst_n==1'b0) begin
            cos_dat[g0*WAVE_DW+:WAVE_DW] <=`U_DLY WAVE_MAX;
        end else if (cke==1'b1) begin
            case({rd_swap_1d[g0], rd_cinv_1d[g0]})
                2'b00   : cos_dat[g0*WAVE_DW+:WAVE_DW] <=`U_DLY rd_pcos_1d[g0];
                2'b01   : cos_dat[g0*WAVE_DW+:WAVE_DW] <=`U_DLY rd_ncos_1d[g0];
                2'b10   : cos_dat[g0*WAVE_DW+:WAVE_DW] <=`U_DLY rd_psin_1d[g0];
                default : cos_dat[g0*WAVE_DW+:WAVE_DW] <=`U_DLY rd_nsin_1d[g0];
            endcase
        end else
            ;
    end

    always@(posedge clk or negedge rst_n) begin
        if (rst_n==1'b0) begin
            sin_dat[g0*WAVE_DW+:WAVE_DW] <=`U_DLY {WAVE_DW{1'b0}};
        end else if (cke==1'b1) begin
            case({rd_swap_1d[g0], rd_sinv_1d[g0]})
                2'b00   : sin_dat[g0*WAVE_DW+:WAVE_DW] <=`U_DLY rd_psin_1d[g0];
                2'b01   : sin_dat[g0*WAVE_DW+:WAVE_DW] <=`U_DLY rd_nsin_1d[g0];
                2'b10   : sin_dat[g0*WAVE_DW+:WAVE_DW] <=`U_DLY rd_pcos_1d[g0];
                default : sin_dat[g0*WAVE_DW+:WAVE_DW] <=`U_DLY rd_ncos_1d[g0];
            endcase
        end else
            ;
    end
end endgenerate

assign cos_ary = tab_f(1'b1);
assign sin_ary = tab_f(1'b0);

always@(*) begin:ROM
    integer i;

    for(i=0; i<TAB_NUM; i=i+1) begin
        cos_rom[i] = cos_ary[i*TAB_DW+:TAB_DW];
        sin_rom[i] = sin_ary[i*TAB_DW+:TAB_DW];
    end
end

function [TAB_NUM*TAB_DW-1:0] tab_f;
    input           typ;    // 1'b1: cos(x); 1'b0: sin(x)

    reg [TAB_DW:0]  val;
    integer         i;
    real            ph;
    begin
        for (i=0; i<TAB_NUM; i=i+1) begin
            ph = (PI/4)*(i+0.5)/TAB_NUM;

            if (typ==1'b1)
                val = $cos(ph)*(WAVE_AM*2.0);
            else
                val = $sin(ph)*(WAVE_AM*2.0);

            tab_f[i*TAB_DW+:TAB_DW] = val[1+:TAB_DW] + val[0];
        end
    end
endfunction

//################################################################################
// ASSERTION
//################################################################################

`ifdef CBB_ASSERT_ON
// synopsys translate_off


// synopsys translate_on
`endif

endmodule
